#include "config.h"

#include <sys/file.h>
#include <sys/types.h>
#include <regex.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <sys/vfs.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/statvfs.h>
#include <dirent.h>

#define DBG_SUBSYS S_LIBINTERFACE

#include "sysy_lib.h"
#include "ynet_rpc.h"
#include "env.h"
#include "lichstor.h"
#include "minirpc.h"
#include "metadata.h"
#include "main_loop.h"
#include "storage.h"
#include "get_version.h"
#include "configure.h"
#include "fence.h"
#include "core.h"
#include "replica.h"
#include "lich_nfs.h"
#include "lich_nbd.h"
#include "lich_sheepdog.h"
#include "lichbd_rpc.h"
#include "chunk.h"
#include "iscsid.h"
#include "net_global.h"
#include "net_vip.h"
#include "volume_ctl.h"
#include "constdef.h"
#include "cpuset.h"

#ifdef NVMF
#include "nvmf.h"
#endif

static uint64_t max_chunk = 0;
//static int tmpmode = 0;

static sem_t exit_sem;

static void usage()
{
        printf("init: \n");
        printf("    lichd --init --home /data --clustername bigcluster\n");
        printf("run:\n");
        printf("    lichd --home /data\n");
        printf("    lichd --tmpmode\n");
        return;
}

static void lichd_monitor_handler(int sig)
{
        DINFO("monitor got signal %d, dump...\n", sig);
        //exit(0);
}

static void lichd_exit_handler(int sig)
{
        DINFO("got signal %d, prepare exit, please waiting\n", sig);

        srv_running = 0;

        netvip_destroy();

        //volume_ctl_destroy_all();

        sem_post(&exit_sem);
}

static void signal_handler(int sig)
{
        (void) sig;

        DINFO("got signal %d, load %ju\n", sig, jobdock_load());

        netable_iterate();
        analysis_dumpall();

        /**
         * @todo dump memory:
         *
         * memory usage:
         * - core hugepage
         * - public hugepage
         * - volume_proto cache
         * - replica_srv cache
         * - schedule tasks
         */
        uint64_t volume_proto_memory, replica_srv_memory, core_memory, total;

        volume_ctl_dump_memory(&volume_proto_memory);
        replica_srv_dump_memory(&replica_srv_memory);
        core_dump_memory(&core_memory);

        // MUST use 1UL
        total = (1UL + cpuset_useable()) * gloconf.memcache_seg * gloconf.memcache_count +
                volume_proto_memory +
                replica_srv_memory +
                core_memory;

        DINFO("core %d\n", cpuset_useable());
        DINFO("volume proto memory %.3f MB\n", 1.0 * volume_proto_memory/BYTES_PER_MB);
        DINFO("replica srv memory %.3f MB\n", 1.0 * replica_srv_memory/BYTES_PER_MB);
        DINFO("core memory %.3f MB\n", 1.0 * core_memory/BYTES_PER_MB);
        DINFO("memcache_seg %u memcache_count %u total memory %.3f MB\n",
              gloconf.memcache_seg,
              gloconf.memcache_count,
              1.0 * total/BYTES_PER_MB);
}

static int __lichd_create_workdir(const char *home, const char *clustername)
{
        int ret;
        char path[MAX_PATH_LEN];

        snprintf(path, MAX_NAME_LEN, "%s/node", home);
        ret = node_create_workdir(path, clustername);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        snprintf(path, MAX_PATH_LEN, "%s/chunk", home);
        ret = path_validate(path, YLIB_ISDIR, YLIB_DIRCREATE);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

static void __lichd_set_deleting()
{
        cluster_set_deleting(1);

        DINFO("set lich deleting\n");
}

static void __lichd_unset_deleting()
{
        cluster_set_deleting(0);

        DINFO("unset lich deleting\n");
}

static int __check_deleting()
{
        int ret, fd;
        char path[MAX_PATH_LEN];
        struct stat stbuf;

        snprintf(path, MAX_PATH_LEN, "%s/deleting", ng.home);

        ret = stat(path, &stbuf);
        if (ret < 0) {
                ret = errno;
                if (ret == ENOENT)
                        goto out;
                else
                        EXIT(ret);
        }

        fd = open(path, O_RDONLY);
        if (fd < 0) {
                ret = errno;
                if (ret == ENOENT)
                        goto out;
                else
                        EXIT(ret);
        }

retry:
        ret = flock(fd, LOCK_EX | LOCK_NB);
        if (ret < 0) {
                ret = errno;
                if (ret == EWOULDBLOCK) {
                        __lichd_set_deleting();
                        sleep(1);
                        goto retry;
                }
        }

        __lichd_unset_deleting();

        unlink(path);

        close(fd);

out:
        return 0;
//err_ret:
//        return ret;
}

static int __check_disk_writeable(int flag)
{
        int ret, fd;
        char path[MAX_PATH_LEN];

        snprintf(path, MAX_PATH_LEN, "%s/status/check_disk_writeable", ng.home);
        if (flag & O_CREAT) {
                fd = open(path, flag, 0644);
                if (fd < 0) {
                        ret = errno;
                        GOTO(err_ret, ret);
                }
        } else {
                fd = open(path, flag);
                if (fd < 0) {
                        ret = errno;
                        GOTO(err_ret, ret);
                }
        }

        close(fd);

        return 0;
err_ret:
        return ret;
}

static int __check_disk_writeable_retry(int flag)
{
        int ret, retry = 0;

retry:
        ret = __check_disk_writeable(flag);
        if (unlikely(ret)) {
                DWARN("disk check writeable fail, ret:%d(%s), retry %d\n", ret, strerror(ret), retry);
                USLEEP_RETRY(err_ret, ret, retry, retry, 30, (100 * 1000));
        }

        return 0;
err_ret:
        return ret;
}

#if ENABLE_START_PARALLEL
static int __service_start()
{
        int ret, retry = 0;

        ret = netable_start();
        if (unlikely(ret))
                GOTO(err_ret, ret);
        
        DINFO("netable started\n");
        
        while (1) {
                if (nid_cmp(net_getnid(), net_getadmin()) == 0) {
                        DINFO("connect master started\n");
                        break;
                }

                ret = network_connect_master();
                if (unlikely(ret)) {
                        DINFO("connect to master fail, wait %u\n", retry);
                        retry++;
                        continue;
                }

                DINFO("connect master started\n");
                break;
        }

        ret = minirpc_start();
        if (unlikely(ret))
                GOTO(err_ret, ret);
        
        DINFO("minrpc started\n");
        
        ret = rpc_start();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        DINFO("rpc started\n");

        ret = conn_register();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        DINFO("conn started\n");

        ret = network_connect_master();
        if (unlikely(ret))
                GOTO(err_ret, ret);
        
        return 0;
err_ret:
        return ret;
}
#endif

static int __lichd_run(void *_home)
{
        int i, ret, lockfd, step = 0;
        char path[MAX_PATH_LEN];
        const char *home = _home;
        struct stat stbuf;

        signal(SIGUSR1, signal_handler);
        signal(SIGUSR2, lichd_exit_handler);
        signal(SIGTERM, lichd_exit_handler);
        signal(SIGHUP, lichd_exit_handler);
        signal(SIGKILL, lichd_exit_handler);
        signal(SIGINT, lichd_exit_handler);
        
        for(i = 0; i < YLOG_TYPE_MAX; i++){
                ylog_reset(i);
                ylog_set_parent(i, 0);
        }

        snprintf(path, MAX_NAME_LEN, "%s/status/status.pid", home);
        ret = daemon_pid(path);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        lockfd = daemon_lock(path);
        if (lockfd < 0) {
                ret = -lockfd;
                GOTO(err_ret, ret);
        }

        snprintf(path, MAX_NAME_LEN, "%s/skip", home);
        ret = stat(path, &stbuf);
        if (ret == 0) {
                DWARN("%s skiped\n", home);
                EXIT(EPERM);
        }

        snprintf(path, MAX_NAME_LEN, "%s/deleted", home);
        ret = stat(path, &stbuf);
        if (ret == 0) {
                DWARN("%s deleted\n", home);
                EXIT(EPERM);
        }

        ANALYSIS_BEGIN(0);
#ifdef NVMF
        if (gloconf.nvmf) {
                ret = lich_nvmf_tgt_init();
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }
#endif

        ret = env_init(home);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ANALYSIS_END(0, 1000 * 100, NULL);

        ANALYSIS_BEGIN(1);
        snprintf(path, MAX_NAME_LEN, "%s/node", home);
        ret = node_init(path);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ANALYSIS_END(1, 1000 * 100, NULL);

        ANALYSIS_BEGIN(2);
        ret = stor_init(home, max_chunk);
        if (unlikely(ret))
                GOTO(err_ret, ret);

#if ENABLE_START_PARALLEL
        ret = __service_start();
        if (unlikely(ret))
                GOTO(err_ret, ret);
#endif
        
        ANALYSIS_END(2, 1000 * 100, NULL);
        
#ifdef USE_ROW2
        ret = lsv_conf_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);
#endif

        conf_dump();

        if (gloconf.iscsid && !sanconf.iscsi_gateway) {
                int driver = 0;

                //if(gloconf.rdma)        //todo. for both running.
                        driver |= ISCSID_DRIVER_ISER;
                //else
                        driver |= ISCSID_DRIVER_TCP;

                ret = iscsid_srv(driver);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else {
                DINFO("iscsi  srv disabled\n");
        }

#if 0
        if (gloconf.nfsd) {
                ret = nfsd_srv();
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }
#endif

        if (gloconf.nbd) {
                ret = nbd_srv();
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        if (gloconf.lichbd) {
                ret = lichbd_srv_init();
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        if (gloconf.sheepdog) {
                ret = sheepdog_srv();
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        if (gloconf.backtrace) {
                DINFO("gloconf.backtrace set goto 1\n");

                ret = _set_text_direct(DGOTO_PATH, "1", strlen("1") + 1, O_CREAT | O_TRUNC | O_SYNC);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        ret = env_update_status("running", -1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

#if 0
        ret = __register_disk_queue_check();
        if (unlikely(ret))
                GOTO(err_ret, ret);
#endif

        ret = __check_disk_writeable_retry(O_CREAT | O_RDWR);
        if (unlikely(ret)) {
                if (ret == ENOSPC) {
                        GOTO(err_ret, ret);
                } else {
                        DERROR("disk check ret (%d) %s\n", ret, strerror(ret));
                        EXIT(EIO);
                }
        }

#if 0
        ret = prof_rpc_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = prof_net_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = prof_vm_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = prof_dio_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);
#endif

        while (srv_running) {
                if (step % 30  == 0) {
                        fence_test();
                }

                if (step % 5  == 0) {
                        __check_deleting();
                }

                if (step % 2  == 0) {
                        ret = __check_disk_writeable_retry(O_RDWR);
                        if (unlikely(ret)) {
                                DERROR("disk check writeable ret (%d) %s\n", ret, strerror(ret));
                                EXIT(EIO);
                        }
                }
                step ++;

                ret = _sem_timedwait1(&exit_sem, 2);
                if (unlikely(ret)) {
                        if (ret == ETIMEDOUT)
                                continue;
                        else
                                GOTO(err_ret, ret);
                }
        }

        ret = env_update_status("stopping", -1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        //stor_destroy();
        node_shutdown();

        snprintf(path, MAX_NAME_LEN, "%s/dirty", home);
        unlink(path);

        ret = env_update_status("stopped", -1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        DINFO("service exit\n");
        return 0;
err_ret:
        DERROR("service exit with error %d\n", ret);
        return ret;
}

#define SYSYTEM_MAXOPENFILE 1048576

int main(int argc, char *argv[])
{
        int ret, daemon = 1, initenv = 0, c_opt, nomonitor = 0;
        char *home = NULL, *clustername = NULL;
        uint64_t size;
        struct statvfs fsbuf;

        while (srv_running) {
                int option_index = 0;

                static struct option long_options[] = {
                        { "init", no_argument, 0, 0},
                        { "home", required_argument, 0, 0},
                        { "clustername", required_argument, 0, 0},
                        { "foreground", no_argument, 0, 'f' },
                        { "help", no_argument, 0, 'h' },
                        { 0, 0, 0, 0 },
                };

                c_opt = getopt_long(argc, argv, "fvh", long_options, &option_index);
                if (c_opt == -1)
                        break;

                switch (c_opt) {
                case 0:
                        switch (option_index) {
                        case 0:
                                DBUG("init\n");
                                initenv = 1;
                                break;
                        case 1:
                                DBUG("home %s\n", optarg);
                                home = optarg;
                                break;
                        case 2:
                                DBUG("clustername %s\n", optarg);
                                clustername = optarg;
                                break;
                        default:
                                fprintf(stderr, "Hoops, wrong op got!\n");
                        }

                        break;
                case 'f':
                        daemon = 0;
                        break;
                case 'v':
                        get_version();
                        goto out;
                case 'h':
                        usage();
                        goto out;
                default:
                        fprintf(stderr, "Hoops, wrong op (%c) got!\n", c_opt);
                        EXIT(1);
                }
        }

        if (home == NULL) {
                usage();
                goto out;
        }

        if (initenv) {
                if (clustername == NULL) {
                        usage();
                        goto out;
                }

                ret = __lichd_create_workdir(home, clustername);
                if (unlikely(ret)) {
                        GOTO(err_ret, ret);
                }

        } else {
                if (clustername) {
                        usage();
                        goto out;
                }

                signal(SIGUSR1, lichd_monitor_handler);
                signal(SIGUSR2, lichd_monitor_handler);
                signal(SIGTERM, lichd_monitor_handler);
                signal(SIGHUP, lichd_monitor_handler);
                signal(SIGKILL, lichd_monitor_handler);
                signal(SIGINT, lichd_monitor_handler);

                ret = statvfs(home, &fsbuf);
                if (ret == -1) {
                        ret = errno;
                        GOTO(err_ret, ret);
                }

                size = (LLU)fsbuf.f_blocks * fsbuf.f_bsize;

                if (size > LICH_NODE_SIZE_MAX) {
                        ret = EOVERFLOW;
                        GOTO(err_ret, ret);
                }

                max_chunk = MAX_CHUNK_DEFAULT;

                ret = env_prep(home, daemon);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                if (nomonitor == 0) {
                        char path[MAX_PATH_LEN];
                        snprintf(path, MAX_NAME_LEN, "%s/status/parent.pid", home);
                        ret = daemon_pid(path);
                        if (unlikely(ret))
                                GOTO(err_ret, ret);

                        ret = env_server_run(daemon, __lichd_run, home);
                        if (unlikely(ret)) {
                                GOTO(err_ret, ret);
                        }
                } else {
                        ret = __lichd_run(home);
                        if (unlikely(ret))
                                GOTO(err_ret, ret);
                }
        }

out:
        return 0;
err_ret:
        EXIT(ret);
}
